<?php

/**
 * @author Evgeny Baranov <evgeny@baranov.eu>
 */
class Document {

    /**
     * @var string
     */
    protected $tmpl = 'index.phtml';

    /**
     * @var DOMDocument
     */
    protected $document;

    /**
     * @var DOMDocumentFragment
     */
    protected $content;

    /**
     * @var array
     */
    protected $scripts = array();

    /**
     * @var array
     */
    protected $styles = array();

    /**
     * @var array
     */
    protected $links = array();

    /**
     * @var array
     */
    protected $meta = array();

    /**
     * @var string
     */
    protected $title;

    /**
     * @var string
     */
    protected $block = 'Main';

    public function __construct() {
        $this->document = new DOMDocument('1.0', 'UTF-8');
    }

    /**
     * @param \DOMDocumentFragment $content
     * @return Document
     */
    public function render(\DOMDocumentFragment $content = null) {
        ob_start();
        $this->includeTemplate();
        $data = ob_get_clean();
        @$this->document->loadHTML($data);
        if ($content) {
            $this->setContent($content);
        }
        $this->process();
        return $this;
    }

    /**
     * @return string
     */
    public function getTemplate() {
        return $this->tmpl;
    }

    protected function includeTemplate() {
        $t = TMPL_DIR . DS . str_replace(NS, DS, \get_called_class()) . DS . $this->getTemplate();
        require $t;
    }

    /**
     * @param DOMDocumentFragment $content
     * @return Document
     */
    public function setContent(DOMDocumentFragment $content) {
        $this->content = $this->document->importNode($content, true);
        return $this;
    }

    /**
     * @param DOMDocumentFragment $content
     * @return Document
     */
    private function process(DOMDocumentFragment $content = null) {
        if ($content) {
            $this->setContent($content);
        }
        return $this->processContent()->processWidget();
    }

    /**
     * @throws Exception
     * @return Document
     */
    private function processWidget() {
        $widgets = $this->document->getElementsByTagName('widget');
        while ($w = $widgets->item(0)) {
            $class = $w->attributes->getNamedItem('class')->nodeValue;
            if (!$class) {
                throw new Exception("Class not set for widget.");
            }
            $class = Factory::getWidget($class, $this->document);
            $new = $class->render($w);
            $w->parentNode->replaceChild($new, $w);
            $widgets = $this->document->getElementsByTagName('widget');
        }

        return $this;
    }

    /**
     * @throws \Exception
     * @return Document
     */
    private function processContent() {
        $tag = $this->document->getElementsByTagName('content')->item(0);
        if (!$tag) {
            throw new \Exception("Content tag not found in template &laquo;{$this->tmpl}&raquo;.");
        }

        $tag->parentNode->replaceChild($this->content, $tag);
        return $this;
    }

    /**
     * @return DOMDocumentFragment
     */
    public function createDocumentFragment() {
        return $this->document->createDocumentFragment();
    }

    public function output() {
        $this->buildHeader();
        echo $this->document->saveHTML();
    }

    /**
     * @param string|array $href
     * @param string $media
     * @return Document
     */
    public function addCSSLink($href, $media = 'all') {
        if (!is_array($href)) {
            $href = array($href);
        }

        foreach ($href as $h) {
            $this->links[] = array(
                'href' => $h,
                'media' => $media,
                'rel' => 'stylesheet',
                'type' => 'text/css'
            );
        }
        return $this;
    }

    /**
     * @param string $href
     * @param string $type
     * @return Document $this
     */
    public function addIcon($href, $type = 'image/x-icon') {
        $this->links[] = array(
            'href' => $href,
            'type' => $type,
            'rel' => 'shortcut icon'
        );
        return $this;
    }

    /**
     * @param string $src
     * @param string $text
     * @return Document
     */
    public function addScript($src, $text = '') {
        if (!is_array($src)) {
            $src = array($src);
        }

        foreach ($src as $s) {
            $this->scripts[] = array(
                'src' => $s,
                'text' => $text,
                'type' => 'text/javascript'
            );
        }

        return $this;
    }

    /**
     * @param string|array $name
     * @param string|array $content
     * @return Document
     */
    public function addMetaName($name, $content) {
        if (!is_array($name)) {
            $name = array($name);
        }

        if (!is_array($content)) {
            $content = array($content);
        }

        $n = max(count($name), count($content));
        for ($i = 0; $i < $n; $i++) {
            $k = isset($name[$n]) ? $name[$n] : $name[0];
            $v = isset($content[$n]) ? $content[$n] : $content[0];
            $this->meta[] = array(
                'name' => $k,
                'content' => $v
            );
        }

        return $this;
    }

    /**
     * @param string|array $httpEquiv
     * @param string|array $content
     * @return Document
     */
    public function addMetaHttp($httpEquiv, $content) {
        if (!is_array($httpEquiv)) {
            $httpEquiv = array($httpEquiv);
        }

        if (!is_array($content)) {
            $content = array($content);
        }

        $n = max(count($httpEquiv), count($content));
        for ($i = 0; $i < $n; $i++) {
            $k = isset($httpEquiv[$n]) ? $httpEquiv[$n] : $httpEquiv[0];
            $v = isset($content[$n]) ? $content[$n] : $content[0];
            $this->meta[] = array(
                'http-equiv' => $k,
                'content' => $v
            );
        }

        return $this;
    }

    public function addMetaOG($name, $content, $pre = 'og') {
        if (!is_array($name)) {
            $name = array($name);
        }

        if (!is_array($content)) {
            $content = array($content);
        }

        foreach ($name as $i => $n) {
            $this->meta[] = array(
                'property' => "{$pre}:{$n}",
                'content' => isset($content[$i]) ? $content[$i] : $content[0]
            );
        }

        return $this;
    }

    /**
     * @param string $t
     * @return Document
     */
    public function setTitle($t) {
        $this->title = $t;
        return $this;
    }

    /**
     * @param string $t
     * @return Document
     */
    public function appendToTitle($t) {
        $this->title = $this->title . ' - ' . $t;
        return $this;
    }

    /**
     * @param string $t
     * @return Document
     */
    public function prependToTitle($t) {
        $this->title = $t . ' - ' . $this->title;
        return $this;
    }

    private function buildHeader() {
        $header = $this->createDocumentFragment();

        // Meta tags
        foreach ($this->meta as $s) {
            $node = $header->ownerDocument->createElement('meta');
            foreach ($s as $k => $v) {
                $node->setAttribute($k, $v);
            }
            $header->appendChild($node);
        }

        // Link tags
        foreach ($this->links as $s) {
            $node = $header->ownerDocument->createElement('link');
            foreach ($s as $k => $v) {
                $node->setAttribute($k, $v);
            }
            $header->appendChild($node);
        }

        // JS Scripts
        foreach ($this->scripts as $s) {
            $node = $header->ownerDocument->createElement('script');
            foreach ($s as $k => $v) {
                if ($k == 'text' && $v) {
                    $cdata = $header->ownerDocument->createCDATASection($v);
                    $node->appendChild($cdata);
                } else {
                    if ($v) {
                        $node->setAttribute($k, $v);
                    }
                }
            }
            $header->appendChild($node);
        }

        // Title
        $title = $header->ownerDocument->createElement('title', $this->title);
        $header->appendChild($title);


        $this->document->getElementsByTagName('head')->item(0)->appendChild($header);
    }

    protected function T($key) {
        return _t($key, $this->block);
    }
}